package cn.changeforyou.web.dao.mybatis.ext.extBuilder.writer;

import cn.changeforyou.base.exception.ExceptionFactory;
import cn.changeforyou.web.dao.mybatis.ext.exception.DaoExceptionEnum;
import cn.changeforyou.web.dao.mybatis.ext.extBuilder.SqlContext;
import cn.changeforyou.web.dao.mybatis.ext.extBuilder.builder.SelectBuilder;
import cn.changeforyou.web.dao.mybatis.ext.extBuilder.sqlUtils.WhereUtils;
import cn.changeforyou.web.dao.mybatis.ext.metaInfo.ConditionCombineFieldMetaInfo;
import cn.changeforyou.web.dao.mybatis.ext.metaInfo.ConditionFieldMetaInfo;
import cn.changeforyou.web.dao.mybatis.ext.metaInfo.FieldMetaInfo;
import cn.changeforyou.web.dao.mybatis.ext.metaInfo.condition.CombineAnnotationEnum;
import cn.changeforyou.web.dao.mybatis.ext.metaInfo.condition.ConditionType;
import cn.changeforyou.web.utils.tree.Tree;
import cn.hutool.core.collection.CollUtil;
import org.springframework.stereotype.Component;

import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author zhyu
 * @version 1.0
 * @date 2022/3/4 15:23
 */
@Component
public class WhereConditionSqlWriter extends AbstractSqlWriter {

    @Override
    void doWriteSql(SqlContext context) {

        switch (context.getMappedStatementParameterType()) {
            case selectByCondition:
            case updateDtoByCondition:
            case updateEntityByCondition:
            case updateDtoIgnoreNull:
            case updateDto:
            case updateEntity:
            case updateEntityIgnoreNull:
            case deleteByEntity:
                buildSelectByCondition(context);
                break;
            default:
                throw ExceptionFactory.jsonException(DaoExceptionEnum.CODE_ERROR);
        }
    }

    @Override
    public SentenceEnum getSentenceEnum() {
        return SentenceEnum.where;
    }

    private void buildSelectByCondition(SqlContext context) {
        SqlBuilder sql = context.getSql();
        context.setScriptModel(true);
        sql.append(" <where>");
        Tree<String> whereTrees = context.getWhereTrees();
        boolean hasOr = context.isHasOr();
        boolean hasBracket = context.isHasBracket();
        boolean needIfTest = !(hasOr || hasBracket);
        context.setNeedIfTest(needIfTest);

        for (Tree<String> node : whereTrees.getChildren()) {
            writeOneNode(node, context);
        }
        sql.append(" </where>");
    }

    /**
     * 处理单节点
     *
     * @param node
     * @param context
     */
    private void writeOneNode(Tree<String> node, SqlContext context) {
        SqlBuilder sql = context.getSql();
        boolean needIfTest = context.isNeedIfTest();
        Object o = node.get(SelectBuilder.META_INFO);
        if (o instanceof FieldMetaInfo) {
            dealFieldMetaInfo(context, (FieldMetaInfo) o);
        } else if (o instanceof ConditionFieldMetaInfo) {
            dealConditionFieldMetaInfo(context, needIfTest, (ConditionFieldMetaInfo) o);
        } else if (o instanceof ConditionCombineFieldMetaInfo) {
            dealConditionCombineFieldMetaInfo(node, context, needIfTest, (ConditionCombineFieldMetaInfo) o);
        }
    }

    /**
     * 处理联合类型
     *
     * @param node
     * @param context
     * @param needIfTest
     * @param o
     */
    private void dealConditionCombineFieldMetaInfo(Tree<String> node, SqlContext context, boolean needIfTest, ConditionCombineFieldMetaInfo o) {
        ConditionCombineFieldMetaInfo conditionCombineFieldMetaInfo = o;
        SqlBuilder sql = context.getSql();
        if (conditionCombineFieldMetaInfo.getType() == CombineAnnotationEnum.between) {
            dealBetween(node, context, needIfTest, conditionCombineFieldMetaInfo);
        } else if (conditionCombineFieldMetaInfo.getType() == CombineAnnotationEnum.bracketWrapper) {
            boolean or = o.isOr();
            sql.append(or ? " or " : " and ");
            sql.append(" (");
            for (Tree<String> child : node.getChildren()) {
                writeOneNode(child, context);
            }
            sql.append(" )");

        }
    }

    /**
     * 处理between
     *
     * @param node
     * @param context
     * @param needIfTest
     * @param conditionCombineFieldMetaInfo
     */
    private void dealBetween(Tree<String> node, SqlContext context, boolean needIfTest, ConditionCombineFieldMetaInfo conditionCombineFieldMetaInfo) {
        Set<String> expression = new HashSet<>(3);
        Set<ConditionFieldMetaInfo> param = new HashSet<>(3);

        preDealBetween(node, conditionCombineFieldMetaInfo, expression, param);

        if (needIfTest) {
            WhereUtils.buildIfTestPrefix(context, param.toArray(new ConditionFieldMetaInfo[param.size()]));
        }

        Map<String, ConditionFieldMetaInfo> key2MetaInfoMap =
                param.stream().collect(Collectors.toMap(ConditionFieldMetaInfo::getKey, x -> x));

        WhereUtils.buildBetween(context, key2MetaInfoMap, expression, conditionCombineFieldMetaInfo);

        if (needIfTest) {
            WhereUtils.buildIfTestPostfix(context);
        }
    }

    /**
     * 预处理between
     * @param node
     * @param conditionCombineFieldMetaInfo
     * @param expression
     * @param param
     */
    private void preDealBetween(Tree<String> node, ConditionCombineFieldMetaInfo conditionCombineFieldMetaInfo, Set<String> expression, Set<ConditionFieldMetaInfo> param) {
        String leftBetween = conditionCombineFieldMetaInfo.getLeftBetween();
        String leftAnd = conditionCombineFieldMetaInfo.getLeftAnd();
        String rightAnd = conditionCombineFieldMetaInfo.getRightAnd();


        expression.add(leftAnd);
        expression.add(leftBetween);
        expression.add(rightAnd);
        List<Tree<String>> children = node.getChildren();

        if (CollUtil.isNotEmpty(children)) {
            //哪怕是空参也必须在子节点中, 不在的必定是表达式
            //获取between的表达式是参数还是表达式

            //目前between只支持单一的字段或者表达式
            for (Tree<String> child : children) {
                expression.remove(child.getId());
                Object oo = child.get(SelectBuilder.META_INFO);
                boolean notSupport = false;
                if (oo instanceof ConditionFieldMetaInfo) {
                    ConditionFieldMetaInfo metaInfo = (ConditionFieldMetaInfo) oo;
                    if (metaInfo.getType() != ConditionType.empty) {
                        notSupport = true;
                    }
                    param.add(metaInfo);
                } else {
                    notSupport = true;
                }
                if (notSupport) {
                    throw ExceptionFactory.jsonException(DaoExceptionEnum.BETWEEN_JUST_SUPPORT_EMPTY_FIELD);
                }
            }
        }
    }

    /**
     * 处理条件类型
     *
     * @param context
     * @param needIfTest
     * @param o
     */
    private void dealConditionFieldMetaInfo(SqlContext context, boolean needIfTest, ConditionFieldMetaInfo o) {
        if (o.getType() == ConditionType.empty) {
            return;
        }
        ConditionFieldMetaInfo metaInfo = o;
        if (needIfTest) {
            WhereUtils.buildIfTestPrefix(context, metaInfo);
        }

        WhereUtils.buildOperatorExpression(context, metaInfo);

        if (needIfTest) {
            WhereUtils.buildIfTestPostfix(context);
        }
    }

    /**
     * 处理字段类型
     *
     * @param context
     * @param o
     */
    private void dealFieldMetaInfo(SqlContext context, FieldMetaInfo o) {
        FieldMetaInfo fieldMetaInfo = o;
        boolean needIfTest = context.isWhereNeedIfTest();

        if (needIfTest) {
            WhereUtils.buildIfTestPrefix(context, fieldMetaInfo);
        }

        WhereUtils.buildOperatorExpression(context, fieldMetaInfo);

        if (needIfTest) {
            WhereUtils.buildIfTestPostfix(context);
        }
    }
}
